﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;

namespace JoinBoxCurrency
{
    public class AssemblyDependent
    {
        string _dllFile;
        /// <summary>
        /// cad程序域依赖_内存区(不可以卸载)
        /// </summary>
        private Assembly[] _cadAs;

        /// <summary>
        /// cad程序域依赖_映射区(不可以卸载)
        /// </summary>
        private Assembly[] _cadAsRef;

        /// <summary>
        /// 加载DLL成功后获取到的程序集
        /// </summary>
        public Assembly MyLoadAssembly { get; private set; }

        /// <summary>
        /// 当前域加载事件
        /// </summary>
        public event ResolveEventHandler CurrentDomainAssemblyResolveEvent;

        /// <summary>
        /// 加载dll的和相关的依赖
        /// </summary>
        /// <param name="dllFile"></param>
        public AssemblyDependent(string dllFile)
        {
            _dllFile = dllFile;

            //cad程序集的依赖
            _cadAs = AppDomain.CurrentDomain.GetAssemblies();

            //映射区
            _cadAsRef = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();

            //运行时出错的话,就靠这个事件来解决
            if (CurrentDomainAssemblyResolveEvent != null)
            {
                AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainAssemblyResolveEvent;
            }
            else
            {
                AppDomain.CurrentDomain.AssemblyResolve += RunTimeCurrentDomain.DefaultAssemblyResolve;
            }
        }

        /// <summary>
        /// 返回的类型,描述加载的错误
        /// </summary>
        public class LoadDllMessage
        {
            public string Path;
            public bool LoadYes;

            public LoadDllMessage(string path, bool loadYes)
            {
                Path = path;
                LoadYes = loadYes;
            }
        }

        /// <summary>
        /// 字节加载
        /// </summary>
        /// <param name="_dllFile"></param>
        /// <returns></returns>
        public LoadDllMessage[] Load()
        {
            var loadYesList = new List<LoadDllMessage>();
            if (!File.Exists(_dllFile))
            {
                return loadYesList.ToArray();
            }

            //查询加载链之后再逆向加载,确保前面不丢失
            var allRefs = GetAllRefPaths(_dllFile);
            allRefs.Reverse();

            foreach (var path in allRefs)
            {
                //路径转程序集名
                string assName = AssemblyName.GetAssemblyName(path).FullName;
                //路径转程序集名
                Assembly assembly = _cadAs.FirstOrDefault((Assembly a) => a.FullName == assName);
                if (assembly == null)
                {
                    //为了实现Debug时候出现断点,见链接,加依赖
                    // https://www.cnblogs.com/DasonKwok/p/10510218.html
                    // https://www.cnblogs.com/DasonKwok/p/10523279.html

                    //实现字节加载 
                    var buffer = File.ReadAllBytes(path);
                     
#if false 
                    var dir = Path.GetDirectoryName(path);
                    var pdbName = Path.GetFileNameWithoutExtension(path) + ".pdb";
                    var pdbFullName = Path.Combine(dir, pdbName);
                    if (File.Exists(pdbFullName))
                    {
                        var pdbbuffer = File.ReadAllBytes(pdbFullName);
                        MyLoadAssembly = Assembly.Load(buffer, pdbbuffer);
                    }
                    else
                    {
                        MyLoadAssembly = Assembly.Load(buffer);
                    } 
#endif

#if true  //由于直接启动cad才能调试,所以可能并不能
#if DEBUG
                    var dir = Path.GetDirectoryName(path);
                    var pdbName = Path.GetFileNameWithoutExtension(path) + ".pdb";
                    var pdbFullName = Path.Combine(dir, pdbName);
                    if (File.Exists(pdbFullName))
                    {
                        var pdbbuffer = File.ReadAllBytes(pdbFullName);
                        MyLoadAssembly = Assembly.Load(buffer, pdbbuffer);
                    }
                    else
                    {
                        MyLoadAssembly = Assembly.Load(buffer);
                    }
#else
                    MyLoadAssembly =  Assembly.Load(buffer);
#endif
#endif
                    loadYesList.Add(new LoadDllMessage(path, true));//加载成功
                }
                else
                {
                    loadYesList.Add(new LoadDllMessage(path, false));//版本号没变不加载
                }
            }
            return loadYesList.ToArray();
        }


        /// <summary>
        /// 获取加载链
        /// </summary>
        /// <param name="dll"></param>
        /// <param name="dlls"></param>
        /// <returns></returns>
        List<string> GetAllRefPaths(string dll, List<string> dlls = null)
        {
            dlls = dlls ?? new List<string>();
            //如果含有 || 不存在文件
            if (dlls.Contains(dll) || !File.Exists(dll))
            {
                return dlls;
            }
            dlls.Add(dll);

            //路径转程序集名
            string assName = AssemblyName.GetAssemblyName(dll).FullName;

            //在当前程序域的assemblyAs内存区和assemblyAsRef映射区找这个程序集名
            Assembly assemblyAs = _cadAs.FirstOrDefault((Assembly a) => a.FullName == assName);
            Assembly assemblyAsRef;

            //内存区有表示加载过
            //映射区有表示查找过但没有加载(一般来说不存在.只是debug会注释掉Assembly.Load的时候用来测试)
            if (assemblyAs != null)
            {
                assemblyAsRef = assemblyAs;
            }
            else
            {
                assemblyAsRef = _cadAsRef.FirstOrDefault((Assembly a) => a.FullName == assName);

                //内存区和映射区都没有的话就把dll加载到映射区,用来找依赖表
                assemblyAsRef = assemblyAsRef ?? Assembly.ReflectionOnlyLoad(File.ReadAllBytes(dll));
            }

            //遍历依赖,如果存在dll拖拉加载目录就加入dlls集合
            foreach (var assemblyName in assemblyAsRef.GetReferencedAssemblies())
            {
                //dll拖拉加载路径-搜索路径(可以增加到这个dll下面的所有文件夹?)
                string directoryName = Path.GetDirectoryName(dll);

                var path = directoryName + "\\" + assemblyName.Name;
                var paths = new string[]
                {
                    path + ".dll",
                    path + ".exe"
                };
                foreach (var patha in paths)
                {
                    GetAllRefPaths(patha, dlls);
                }
            }
            return dlls;
        }

        /// <summary>
        /// 调用dll的方法
        /// </summary> 
        /// <param name="nameSpace">命名空间(null就搜索所有)</param>
        /// <param name="className">类名(null就搜索所有)</param>
        /// <param name="lpProcName">方法名</param>
        /// <param name="objArr">方法参数</param>
        /// <returns>错误信息</returns>
        public string Invoke(string nameSpace, string className, string lpProcName, params object[] objArr)
        {
            string ssc = "";
            try
            { // 载入程序集
                Type[] type = MyLoadAssembly.GetTypes();
                foreach (Type t in type)
                {// 查找要调用的命名空间及类
                    bool ns = false;
                    bool cn = false;
                    if (nameSpace == null)
                    {
                        ns = true;
                    }
                    else
                    {
                        ns = t.Namespace == nameSpace;
                    }
                    if (className == null)
                    {
                        cn = true;
                    }
                    else
                    {
                        cn = t.Name == className;
                    }
                    if (ns && cn)
                    {// 查找要调用的方法并进行调用
                        MethodInfo m = t.GetMethod(lpProcName);
                        if (m != null)
                        {
                            //创建这个方法
                            object o = Activator.CreateInstance(t, objArr);
                            //方法于反射出来的命名空间的类下,调用
                            ssc = m.Invoke(o, null) as string;
                        }
                        else
                            ssc = "装载出错!";
                    }
                }
            }
            catch (NullReferenceException e)
            {
                ssc = e.Message;
            }
            return ssc;
        }



      


    }
}